#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define HOSTNAME_MAX 1024
#define FORMAT "[%s@%s:%s]# "

// shell定义的全局变量

// 全局的命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;

char cwd[1019];
char cwdenv[1024]; 

// 环境变量表
#define MAX_ENVS 1024
char *g_env[MAX_ENVS];
int g_envs = 0;

// 最后一个程序退出码
int lastcode = 0;

const char *GetUserName()
{
    const char * name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()
{
    char hostname[HOSTNAME_MAX];
    return (gethostname(hostname, HOSTNAME_MAX) == -1) ? "None" : hostname;
}

const char *GetPwd()
{
    //const char * pwd = getenv("PWD");
    const char *pwd = getcwd(cwd, sizeof(cwd)); // 通过系统调用获取
    if(pwd != NULL)
    {   // 把自己的环境变量导给进程
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    return home == "" ? NULL : home;
}

// 对路径做包装
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "BUG";
    return dir.substr(pos+1);
}

// 制作命令行提示符
void MakeCommandline(char cmd_prompt[], int size)
{
    // 设置式化输入
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}

// 打印命令行提示符
void PrintCommandline()
{
    char prompt[COMMAND_SIZE];
    MakeCommandline(prompt, COMMAND_SIZE);
    printf("%s", prompt);
    fflush(stdout);
}

// 获取用户输入命令
bool GetCommandline(char *out, int size)
{
    // 从标准输入流获取命令，其实就是字符串
    char* c = fgets(out, size, stdin);

    if(c == NULL) return 1;
    out[strlen(out)-1] = '\0'; // 清理\n, 至少会按一次回车，所以不会出错

    if(strlen(out) == 0) return false;// 什么都没输入
    return true;
}

// 3. 命令行分析
bool CommandParse(char *commandline)
{
    // "ls -a -l" -> "ls" "-a" "-l"
    // 字符串切割 strtok()函数
#define SEP " "
    g_argc = 0;
    // 第一次调用：传入要分割的字符串和分隔符
    g_argv[g_argc++] = strtok(commandline, SEP);
    // 后续调用：传入 NULL 和分隔符，继续分割同一字符串
    while(g_argv[g_argc++] = strtok(NULL, SEP));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArray()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("%d\n",g_argc);
}

// 从父shell1获取环境变量表，本来是要从配置文件获取的
void InitEnv()
{
    extern char **environ;
    memset(g_env, 0, sizeof(g_env)); // 初始化
    g_envs = 0;

    // 1.获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        // (1)申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        // (2)拷贝到我们的环境变量表
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    // 测试：添加了一个新的环境变量
    g_env[g_envs++] = (char*)"HAHA=for_test";
    g_env[g_envs] = NULL; // 表的最后一个成员必须是NULL

    // 2.获取到的环境变量导入shell的表中
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
}

// 内键命令
bool CD()
{

    if(g_argc == 1) // 只有一个cd时
    {
        std::string home = GetHome();
         if(home.empty()) return true;
        chdir(home.c_str());
     }
    else
    {
        std::string where = g_argv[1];
        if(where == "-")
        {}
        else if(where == "~")
        {}
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

bool Echo()
{
    if(g_argc == 2)
    {
        //
        std::string opt = g_argv[1];
        if(opt == "$?") // eg1: echo $?
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;
            return true;
        }
        if(opt[0] == '$') // eg2: echo $PATH
        {
            std::string env_name = opt.substr(1);
            const char * env_value = getenv(env_name.c_str()); // 获取环境变量的值
            if(env_value) 
                std::cout << env_value << std::endl;
        }
    }
    return false;
}

// 4. 检测并执行内键命令
bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
       return CD();
    }
    else if(cmd == "echo")
    {
       return Echo();
    }
    return false;
}

// 5.执行命令
int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child, 程序替换
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 1;
}
// 释放通过malloc分配的环境变量内存
void FreeEnvMemory()
{
    for(int i = 0; i < g_envs; i++) {
        if(g_env[i] != NULL) {
            free(g_env[i]);  // 释放每个环境变量字符串
            g_env[i] = NULL; // 置空指针
        }
    }
    g_envs = 0;  // 重置计数器
}

int main()
{
    // shell启动要从系统中获取环境变量，而我们的环境变量信息要从父shell中获取
    InitEnv();
    // 当然是循环啦！
    while(true)
    {
        // 1、输出命令行提示符
        PrintCommandline();

        // 2. 获取输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandline(commandline, COMMAND_SIZE))
            continue;
        //printf("%s\n", commandline);

        // 3. 命令行分析, 字符串拆分成多个元素
        if(!CommandParse(commandline)) continue;
        //PrintArray();

        // 4.检测并执行内建命令
        if(CheckAndExecBuiltin()) continue;

        // 5. 执行命令
        Execute();
    }
    // 释放空间
    FreeEnvMemory();
    return 0;
}
